# coding=utf-8
from flask import current_app
from utils.views import bp_cache
from datetime import datetime
from urllib.parse import urlparse
from docx import Document
from docx.shared import Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
from typing import List, Dict
import time
import random
import configparser
import sys
import asyncio
import aiohttp
import requests
import os
import fitz  # PyMuPDF
import re
import html


"""
读取配置文件
"""
try:
    cf = configparser.ConfigParser()
    # cf.optionsxform = str  # 若出现不分大小写字符的情况 用这个
    cf.read('./conf/config.ini')
    aikey = cf.get('ai','key')
    apikey = cf.get('api','apikey')
    jainum = cf.get('web','jai_num')
except Exception as e:
    current_app.logger.debug('请确保config.ini是否存在,或配置不正确！')
    current_app.logger.debug(e)
    sys.exit(0)

class aiModel(object):

    # 处理论文，整理成列表格式
    def handle_text(self,items,db):
        for item in items:
            current_app.logger.debug(f"================用户id:{item['user_id']} 主流程id:{item['id']} 子流程id:{item['p_id']} 类型:{item['type']} 处理论文开始=================")
            # 降AI类型
            type = item['type']
            # 查询查重接口返回
            reportApiRes = self.getReportApi(item['submit_id'])
            # 接口错误或文件有问题，跳过
            if 'data' not in reportApiRes:
                # ai率
                ai = 'Unable to detect'
                # 相似率
                similarity = 'Unable to detect'
                # AI报告
                ai_report = ''
                # 查重报告
                plagiarism_report = ''
            else:
                # 查重未完成，跳过
                if not reportApiRes['data']['AI']:
                    current_app.logger.debug("AI报告还未生成")
                    continue
                # ai率
                ai = reportApiRes['data']['AI'].strip()
                # 相似率
                similarity = reportApiRes['data']['similarity'].strip()
                # AI报告
                ai_report = reportApiRes['data']['AIReport'].strip()
                # 查重报告
                plagiarism_report = reportApiRes['data']['plagiarismReport'].strip()
            # 回收状态
            recycle_status = 0
            if item['num'] >= int(jainum) and ai != '0%' and ai != '*%':
                recycle_status = 1
            if 'Unable to detect' in ai:
                recycle_status = 1
            # 查不出AI/流程字数超10次/AI达标，流程改为完成
            if item['num'] >= int(jainum) or 'Unable to detect' in ai or ai == '0%' or (ai == '*%' and type == 0):
                if item['num'] <= 1:
                    recycle_status = 1
                db.execute_many_sqls([
                    ("UPDATE fa_task_jai_process SET status = 1,updatetime = %s,ai = %s,similarity = %s, plagiarism_report = %s,ai_report = %s WHERE id = %s", 
                    (time.time(),ai,similarity,plagiarism_report,ai_report,item['p_id'])),
                    ("UPDATE fa_task_jai SET status = 1,updatetime = %s,recycle_status = %s WHERE id = %s",
                    (time.time(),recycle_status,item['id']))
                ])
                current_app.logger.debug(f"================用户id:{item['user_id']} 主流程id:{item['id']} 子流程id:{item['p_id']} AI率:{ai} 文章已经降到标准 流程完成=================")
                continue
            if not item['task_jai_id']:
                # 开始处理文本
                # 下载pdf到本地
                downloadRes = self.download_file(ai_report)
                current_app.logger.debug("下载ai报告结果")
                current_app.logger.debug(downloadRes)
                if not downloadRes:
                    current_app.logger.debug("下载pdf异常")
                    continue
                # 下载下来的pdf路径
                filepath = downloadRes['filepath']
                # 根据ai报告和文本判断哪些段落需要降AI
                # 如果是*号，获取`降AI段落`字段，如果是空全文降，反之按照段落
                ai_para = item['ai_para']
                if not ai_para:
                    ai_para = ()
                else:
                    ai_para = tuple(map(int, ai_para.split(',')))
                # 拆分原始文章为段落
                paragraphs = self.split_paragraphs(item['content'])
                # 获取原文章有AI的段落
                ai_para = self.mark_highlighted_paragraphs(ai_para,paragraphs,filepath)
                # 根据获取的段落组成新文章
                newtext = "\n\n\n\n".join(
                    paragraphs[index - 1] 
                    for index in ai_para 
                    if 0 <= index - 1 < len(paragraphs)
                )
                # 调用降AI接口
                humanizeAIRes = self.humanizeAI(newtext,item['p_id'])
                if 'error' in humanizeAIRes:
                    current_app.logger.debug("降AI API异常")
                    continue
                dataid = humanizeAIRes['data']['data_id']
                db.execute("UPDATE fa_task_jai_process SET updatetime = %s,ai = %s,similarity = %s, plagiarism_report = %s,ai_report = %s,task_jai_id = %s,para_str = %s WHERE id = %s",
                        (time.time(),ai,similarity,plagiarism_report,ai_report,dataid,','.join(map(str,ai_para)), item['p_id']))
            current_app.logger.debug(f"================用户id:{item['user_id']} 主流程id:{item['id']} 子流程id:{item['p_id']} 处理论文结束=================")
    
    # """
    # 标记原始文章中哪些段落被高亮
    
    # :param original_text: 原始文章文本
    # :param pdf_path: PDF文件路径
    # :return: [{
    #     'paragraph': str,        # 段落内容
    #     'is_highlighted': bool,  # 是否包含高亮
    #     'index': int            # 原始位置索引
    # }]
    # """
    def mark_highlighted_paragraphs(self,ai_para,paragraphs: str, pdf_path: str) -> List[Dict]:
        # 提取PDF中所有高亮文本片段
        highlight_fragments = self.extract_highlight_fragments(pdf_path)
        
        
        # 标记哪些段落包含高亮片段
        marked_paragraphs = [
            {
                'paragraph': para,
                'is_highlighted': self.is_paragraph_highlighted(para, highlight_fragments),
                'index': idx + 1
            }
            for idx, para in enumerate(paragraphs)
        ]
        # 获取所有包含高亮的段落
        highlighted_indices = tuple(
            idx + 1 for idx, p in enumerate(marked_paragraphs)
            if p['is_highlighted']
        )
        if ai_para and not highlighted_indices:
            highlighted_indices = ai_para

        if not ai_para and not highlighted_indices:
            highlighted_indices = tuple(
                idx + 1 for idx, p in enumerate(paragraphs)
                if p
            )
        return highlighted_indices

    # """提取PDF中所有高亮文本片段"""
    def extract_highlight_fragments(self,pdf_path: str) -> List[str]:
        doc = fitz.open(pdf_path)
        highlights = []
        
        for page in doc:
            # 获取本页所有背景矩形
            bg_rects = [
                fitz.Rect(d['rect'])
                for d in page.get_drawings() 
                if d.get('fill') and not d.get('stroke')  # 关键判断：有填充无描边
            ]
            
            # 检查每个文本块是否与背景矩形相交
            for block in page.get_text("blocks"):
                x0, y0, x1, y1, text = block[:5]
                if not text.strip():
                    continue
                    
                block_rect = fitz.Rect(x0, y0, x1, y1)
                if any(bg_rect.intersects(block_rect) for bg_rect in bg_rects):
                    highlights.append(text.strip())
        
        doc.close()
        return highlights

    # """按空行拆分文本为段落"""
    def split_paragraphs(self,text: str) -> List[str]:
        text = re.sub(r'\n+', '\n\n\n\n', text)
        return [html.unescape(p.strip()) for p in re.split(r'\n\s*\n', text) if p.strip()]

    # """判断段落是否包含任何高亮片段"""
    def is_paragraph_highlighted(self,paragraph: str, fragments: List[str]) -> bool:
        para_clean = self.normalize_text(paragraph)
        return any(
            self.normalize_text(fragment) in para_clean 
            for fragment in fragments
        )

    # """标准化文本用于比较（移除空格/换行/大小写）"""
    def normalize_text(self,text: str) -> str:
        return html.unescape(re.sub(r'\s+', '', text).lower())

    # """
    # 下载在线文件到本地，按日期分类存储，覆盖已存在文件
    
    # :param url: 要下载的文件URL
    # :param custom_filename: 可选的自定义文件名
    # :return: 字典 {'success': bool, 'message': str, 'filepath': str, 'filename': str}
    # """
    def download_file(self,url):
        try:
            # 创建以日期命名的下载目录
            today = datetime.now().strftime("%Y-%m-%d")
            download_dir = os.path.join("static", "download", today)
            os.makedirs(download_dir, exist_ok=True)
            # 获取文件流
            response = requests.get(url, stream=True, allow_redirects=True)
            response.raise_for_status()
            # 从URL中提取文件名
            filename = os.path.basename(url)
            # 构建保存路径
            save_path = os.path.join(download_dir, filename)
            # 保存文件（直接覆盖已存在文件）
            with open(save_path, 'wb') as f:
                for chunk in response.iter_content(8192):
                    if chunk:
                        f.write(chunk)
            return {
                'success': True,
                'message': '文件下载成功',
                'filepath': save_path,
                'filename': filename,
                'download_dir': download_dir
            }

        except Exception as e:
            current_app.logger.error(f"ai报告下载异常: {e}")
            return None
        
    # 根据文本创建docx文件
    def create_word_document(self,text):
        # 创建以日期命名的下载目录
        today = datetime.now().strftime("%Y-%m-%d")
        file_dir = os.path.join("static", "docx", today)
        os.makedirs(file_dir, exist_ok=True)
        try:
            """将文本按段落生成Word文档"""
            doc = Document()
            # 设置正文样式
            style = doc.styles['Normal']
            font = style.font
            font.name = 'Times New Roman'
            font.size = Pt(11)
            
            # 分割段落（按两个换行符分割）
            paragraphs = [p.replace('\n',' ').strip() for p in text.split('\n\n') if p.strip()]
            
            # 添加所有段落
            for para in paragraphs:
                # para = html.unescape(para)
                # para = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F]', '', para)
                p = doc.add_paragraph(para)
                p.alignment = WD_ALIGN_PARAGRAPH.LEFT  # 左对齐
            # 获取当前时间戳（毫秒级）
            timestamp_ms = int(time.time() * 1000)
            # 生成8位随机数（可能以0开头）
            random_num = f"{random.randint(0, 99999999):08d}"
            # 组合文件名
            filename = f"{timestamp_ms}_{random_num}.docx"
            # 构建保存路径
            save_path = os.path.join(file_dir, filename)
            # 保存文档
            doc.save(save_path)
            return {
                    'success': True,
                    'message': '文件下载成功',
                    'filepath': save_path,
                    'filename': filename,
                    'file_dir': file_dir
                }
        except Exception as e:
            current_app.logger.error(f"生成docx文件异常: {e}")
            return None
        
    # 查重服务器上传文件接口
    def uploadApi(self,file_path):
        url = "https://api.turnitin.club/api/userccm/essauraUpload"
        # url = "http://fastadmin.test/api/userccm/essauraUpload"
        data = {
            "apikey": apikey,
        }

        # 检查文件是否存在
        if not os.path.exists(file_path):
            current_app.logger.error(f"文件 {file_path} 不存在")
            return {'error':f"文件 {file_path} 不存在"}
        try:
            # 使用 with 自动管理文件
            with open(file_path, "rb") as file:
                files = {
                    "file": (os.path.basename(file_path), file, "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
                }
                
                # 发送请求，设置超时
                response = requests.post(url, data=data, files=files, timeout=30)
                response.raise_for_status()  # 检查 HTTP 错误（4xx/5xx）
                
                result = response.json()
                current_app.logger.debug("uploadApi 返回数据:")
                current_app.logger.debug(result)
                # 校验返回数据结构
                if not isinstance(result, dict):
                    current_app.logger.error("uploadApi 接口返回数据格式错误")
                    return {'error':"接口返回数据格式错误"}
                if result.get("code", 0) != 1:  # 假设 code=0 表示成功
                    current_app.logger.error(f"uploadApi 上传失败: {result.get('msg', '未知错误')}")
                    return {'error':f"上传失败: {result.get('msg', '未知错误')}"}
                else:
                    current_app.logger.info(f"uploadApi 上传成功! 返回数据ID: {result['data']}")
                    return {'data':result['data']}

        except requests.exceptions.RequestException as e:
            current_app.logger.error(f"uploadApi 网络请求失败: {e}")
            return {'error':f"网络请求失败: {e}"}
        except ValueError as e:
            current_app.logger.error(f"uploadApi 数据解析失败: {e}")
            return {'error':f"数据解析失败: {e}"}
        except Exception as e:
            current_app.logger.error(f"uploadApi 未知错误: {e}")
            return {'error':f"未知错误: {e}"}

    # 查重服务器查询报告接口 
    def getReportApi(self,id):
        url = "https://api.turnitin.club/api/userccm/getReport"
        # url = "http://fastadmin.test/api/userccm/getReport"
        data = {
            "apikey": apikey,
            "data_id": id,
        }
        try:
            response = requests.post(url, data=data, timeout=30)
            response.raise_for_status()  # 检查 HTTP 错误（4xx/5xx）
            
            result = response.json()
            current_app.logger.debug("getReportApi 返回数据:")
            current_app.logger.debug(result)
            # 校验返回数据结构
            if not isinstance(result, dict):
                current_app.logger.error("getReportApi 接口返回数据格式错误")
                return {'error':"接口返回数据格式错误"}
            if result.get("code", 0) != 1:  # code=0 表示成功
                current_app.logger.error(f"getReportApi 操作失败: {result.get('msg', '未知错误')}")
                return {'error_msg':f"操作失败: {result.get('msg', '未知错误')}"}
            else:
                return {'data':result['data']}

        except requests.exceptions.RequestException as e:
            current_app.logger.error(f"getReportApi 网络请求失败: {e}")
            return {'error':f"网络请求失败: {e}"}
        except ValueError as e:
            current_app.logger.error(f"getReportApi 数据解析失败: {e}")
            return {'error':f"数据解析失败: {e}"}
        except Exception as e:
            current_app.logger.error(f"getReportApi 未知错误: {e}")
            return {'error':f"未知错误: {e}"}
        
    # 查重服务器查询报告接口 
    def humanizeAI(self,input,system_id):
        url = "https://api.turnitin.club/api/userccm/humanizeAI"
        # url = "http://fastadmin.test/api/userccm/humanizeAI"
        data = {
            "apikey": apikey,
            "system_id": system_id,
            "input": input
        }
        try:
            response = requests.post(url, data=data, timeout=30)
            response.raise_for_status()  # 检查 HTTP 错误（4xx/5xx）
            
            result = response.json()
            current_app.logger.debug("humanizeAI 返回数据:")
            current_app.logger.debug(result)
            # 校验返回数据结构
            if not isinstance(result, dict):
                current_app.logger.error("humanizeAI 接口返回数据格式错误")
                return {'error':"接口返回数据格式错误"}
            if result.get("code", 0) != 1:  # code=0 表示成功
                current_app.logger.error(f"humanizeAI 操作失败: {result.get('msg', '未知错误')}")
                return {'error_msg':f"操作失败: {result.get('msg', '未知错误')}"}
            else:
                return {'data':result['data']}

        except requests.exceptions.RequestException as e:
            current_app.logger.error(f"humanizeAI 网络请求失败: {e}")
            return {'error':f"网络请求失败: {e}"}
        except ValueError as e:
            current_app.logger.error(f"humanizeAI 数据解析失败: {e}")
            return {'error':f"数据解析失败: {e}"}
        except Exception as e:
            current_app.logger.error(f"humanizeAI 未知错误: {e}")
            return {'error':f"未知错误: {e}"}
        
    # 查重服务器查询报告接口 
    def outputText(self,task_id):
        url = "https://api.turnitin.club/api/userccm/outputText"
        # url = "http://fastadmin.test/api/userccm/outputText"
        data = {
            "apikey": apikey,
            "task_id": task_id,
        }
        try:
            response = requests.post(url, data=data, timeout=30)
            response.raise_for_status()  # 检查 HTTP 错误（4xx/5xx）
            
            result = response.json()
            current_app.logger.debug("outputText 返回数据:")
            current_app.logger.debug(result)
            # 校验返回数据结构
            if not isinstance(result, dict):
                current_app.logger.error("outputText 接口返回数据格式错误")
                return {'error':"接口返回数据格式错误"}
            if result.get("code", 0) != 1:  # code=0 表示成功
                current_app.logger.error(f"outputText 操作失败: {result.get('msg', '未知错误')}")
                return {'error_msg':f"操作失败: {result.get('msg', '未知错误')}"}
            else:
                return {'data':result['data']}

        except requests.exceptions.RequestException as e:
            current_app.logger.error(f"outputText 网络请求失败: {e}")
            return {'error':f"网络请求失败: {e}"}
        except ValueError as e:
            current_app.logger.error(f"outputText 数据解析失败: {e}")
            return {'error':f"数据解析失败: {e}"}
        except Exception as e:
            current_app.logger.error(f"outputText 未知错误: {e}")
            return {'error':f"未知错误: {e}"}